package com.example.filter;

import com.example.entity.RestBean;
import com.example.utils.Const;
import com.example.utils.FlowUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.PrintWriter;

/**
 * 限流控制过滤器
 * 该过滤器的主要作用是防止用户高频请求接口，借助Redis进行限流操作，保障系统的稳定性和安全性。
 */
@Slf4j
@Component
@Order(Const.ORDER_FLOW_LIMIT)
public class FlowLimitingFilter extends HttpFilter {

    // 注入StringRedisTemplate，用于与Redis进行交互，存储和读取限流相关的数据
    @Resource
    StringRedisTemplate template;

    // 从配置文件中获取指定时间内最大请求次数限制
    @Value("${spring.web.flow.limit}")
    int limit;

    // 从配置文件中获取计数时间周期，单位可以是秒、分钟等，具体取决于配置
    @Value("${spring.web.flow.period}")
    int period;

    // 从配置文件中获取超出请求限制后的封禁时间，单位同样取决于配置
    @Value("${spring.web.flow.block}")
    int block;

    // 注入FlowUtils工具类，用于执行限流相关的检查逻辑
    @Resource
    FlowUtils utils;

    /**
     * 过滤器的核心处理方法，在请求通过过滤器时被调用。
     * 该方法会对请求进行限流检查，如果请求被限制则返回提示信息，否则继续处理请求。
     *
     * @param request  HttpServletRequest对象，包含客户端的请求信息
     * @param response HttpServletResponse对象，用于设置响应信息
     * @param chain    FilterChain对象，用于将请求传递给下一个过滤器或目标资源
     * @throws IOException      如果在处理请求或响应时发生I/O错误
     * @throws ServletException 如果在处理请求或响应时发生Servlet相关的异常
     */
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取请求的客户端IP地址
        String address = request.getRemoteAddr();
        // 如果请求方法为OPTIONS，并且尝试计数失败（即请求被限制）
        if ("OPTIONS".equals(request.getMethod()) && !tryCount(address))
            // 向客户端写入请求被限制的提示信息
            this.writeBlockMessage(response);
        else
            // 否则，将请求传递给下一个过滤器或目标资源继续处理
            chain.doFilter(request, response);
    }

    /**
     * 尝试对指定IP地址的请求进行计数，如果该IP地址被限制则无法继续访问。
     * 该方法使用Redis来存储和检查请求计数信息。
     *
     * @param address 请求的客户端IP地址
     * @return 如果操作成功（即未被限制）返回true，否则返回false
     */
    private boolean tryCount(String address) {
        // 使用同步块确保同一IP地址的请求计数操作是线程安全的
        synchronized (address.intern()) {
            // 检查Redis中是否存在该IP地址的封禁键，如果存在则表示该IP已被封禁
            if(Boolean.TRUE.equals(template.hasKey(Const.FLOW_LIMIT_BLOCK + address)))
                return false;
            // 构建请求计数器的键名
            String counterKey = Const.FLOW_LIMIT_COUNTER + address;
            // 构建封禁键名
            String blockKey = Const.FLOW_LIMIT_BLOCK + address;
            // 调用FlowUtils工具类的方法进行限流检查
            return utils.limitPeriodCheck(counterKey, blockKey, block, limit, period);
        }
    }

    /**
     * 为响应编写拦截内容，提示用户操作频繁。
     * 该方法会设置响应的状态码和内容类型，并向客户端发送JSON格式的提示信息。
     *
     * @param response HttpServletResponse对象，用于设置响应信息
     * @throws IOException 如果在写入响应信息时发生I/O错误
     */
    private void writeBlockMessage(HttpServletResponse response) throws IOException {
        // 设置响应的状态码为429，表示请求频率过高
        response.setStatus(429);
        // 设置响应的内容类型为JSON，并指定字符编码为UTF-8
        response.setContentType("application/json;charset=utf-8");
        // 获取响应的输出流
        PrintWriter writer = response.getWriter();
        // 向输出流写入JSON格式的提示信息，提示用户请求频率过快
        writer.write(RestBean.failure(429, "请求频率过快，请稍后再试").asJsonString());
    }

}